Flask を使ってみよう
https://gyazo.com/61005ed866fadfd3438134db793f2f17
Flaskは、BSDライセンスで利用できるPythonのマイクロフレームワークで、Ruby のSinatraフレームワークに触発され開発されました。
Flaskの実行には、Werkzeug WSGIツールキットとJinja2テンプレートエンジンが必要になりますが、condaコマンド(あるいはpip)でインストールすると、依存ライブラリとして同時にインストールされます。
Flaskはアイデアは、強固なWebアプリケーションの容易な構築を支援することで、コアな機能としては必用最低限のものしか持っていません。しかし、必要な拡張機能を追加することで、認証やデータベースなどを使用することができます。 このため、Flaskはすべてのプロジェクトに適用することができます。Django を採用する程でもないようなWebプロジェクトではデフォルトの選択となるでしょぅ。
Flaskの軽量でモジュール方式で開発するように設計されているため、開発者のニーズに簡単に適応できます。
Flaskで利用できる便利な機能
組み込みの開発サーバーと高速デバッガー
単体テストの統合サポート
RESTfulリクエストのディスパッチ
Jinja2テンプレート
セキュアCookieのサポート(クライアント側セッション)
WSGI 1.0準拠
Unicodeベース
ORMをプラグインする機能
HTTPリクエスト処理
2010年の発売以来、Flaskは多くのバージョン更新がされていて、今も成長し続けています。
ただし、コントリビューションとして開発されていたFlaskの拡張機能はバージョンに追随できなくなっているものもあります。
インストール
既にCONDA環境になっているのであれば抜けておきましょう。
code: bash
$ conda deactivate
conda環境を作ります。
code: bash
$ conda create -y -n flask python=3.6
$ conda activate flask
関連パッケージをあわせてインストールしておきましょう。
code: bash
$ pip install flask flask-WTF flask-migrate flask-sqlalchemy python-dotenv
Flaskのディレクトリ構造
flask ではモジュールを定義することで、Webアプリケーションを作成してゆきます。
デフォルトのディレクトリ構造は、モジュールと同じディレクトリにテンプレートファイルがあるものとして動作しますが、次のように変更することもできます。
code: Python
app = Flask(__name__, template_folder='path/to/templates')
template_folderに与えたディレクトリにテンプレートファイルを保存します。
ここでは、次のようにディレクトリを作成して、作業ディレクトリを移動しましょう。
code: bash
$ mkdir myapp
$ cd myapp
$ mkdir templates
はじめてのアプリケーション
まず、つぎのテンプレートファイルを templates/index.html として保存しておきましょう。
code: templates/index.html
<html>
<body>
<p>Hello {{ name }}</p>
</body>
</html>
次に、app.py を作成しましょう。
code: app.py
from flask import Flask, render_template
app = Flask(__name__, template_folder='templates')
@app.route('/greeting/<name>')
def index(name):
return render_template('index.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
@app.route() で指定したURLにマッチするアクセスがあると、関数index() が呼び出されます。
Flaskでは、@app.route() でデコレートされた関数を、ビュー関数(View Function) と呼びます。
この時点では、次のディレクトリ構成になっているはずです。
code: bash
myapp
├── app.py
└── templates
└── index.html
次にflask run を実行します。
Flaskはデフォルトでは現在の作業ディレクトリにある app.py を読み込みます。
code: bash
$ flask run
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
Flask単独でHTTPサーバになれますが、警告(WARNING)にあるように、正式に公開するのであれば、WSGIサーバを使うようにしましょう。
このURLでアクセスしてみると、うまくレンダリングされます。
https://gyazo.com/96a68f0c366b2a0f33853f9e77e5f9df
エラーハンドリング
"Jinja2を使ってみよう” で説明したように、Jinja2 はテンプレートを継承することができます。これを使って、無効なURLアクセスをしたときの処理をさせてみましょう。 まず、 base.html を作成します。
code: templates/base.html
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
次のこれを継承する2つのテンプレートファイルを用意します。
まずは、通常にアクセスで使用されるテンプレートファイル index.html を作成します。
code: tempaltes/index.html
{% extends 'base.html' %}
{% block title %} Index {% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block body %}
<h1>Hello Flask</h1>
<p>Welcome to Flas Example.</p>
{% endblock %}
{% extends テンプレートファイル名 %} で指定したテンプレートから継承します。
このテンプレートは base.html を継承していますね。
ブラウザからのアクセスされたときに、URLに該当するコンテンツが存在しないときは
このエラーが発生したときに使用されるテンプレートファイル 404.html を作成しましょう。
code: templates/404.html
{% extends 'base.html' %}
{% block title %} Page Not Found {% endblock %}
{% block body %}
<h1>404 Error :(</h1>
<p>What you were looking for is not found.<p>
<a href="{{ url_for('index') }}">Go to top page</a>
{% endblock %}
index.html と同じく、このテンプレートも base.html を継承していますね。
{{ url_for('index') }} はFlaskでルーティングされるindexを検索して、それに該当するURLに置き換えてくれます。
app.py を次のように作成します。
code: apps/app.py
from flask import Flask, render_template
app = Flask(__name__, template_folder='../templates')
@app.route('/')
def index():
return render_template('index.html')
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
if __name__ == '__main__':
app.run(debug=True)
http://127.0.0.1:5000/ としてアクセスすると期待どおりに Hello Flask が表示されます。
https://gyazo.com/5e4b160cab02fd39245154a692d74190
以前の例で使用したURL http://127.0.0.1:5000/greeting/Freddie でアクセスしてみると、その定義はされていないためエラーになり、Flask はこれをうまく処理できています。
https://gyazo.com/7bdf9ba2878b9fe65d41a39f64402be8
Flask コマンド
flask run
flask を実行します。デフォルトでは現在の作業ディレクトリの app.py を読み込もうとします。もし、違う名前でモジュールを作成したのであれば、環境変数にセットします。
例えば、app.py を apps/greeting.py として保存したとすると、次のように実行します。
code: bash linux / bash on windows
$ export FLASK_APP=apps/greeting.py
$ flask run
code: WindowsのCMD
set FLASK_APP=apps\greeting.py
flask run
code: WindowsのPowerShell
$env:FLASK_APP = "apps/greeting.py"
flask run
実は、flask run の代わりに、直接 Python で実行しても動作します。
code: bash
$ python apps/greeting.py
flask routes
定義されているアプリケーションのURLのリストを表示します。
/statis は画像などテンプレートとモジュール以外のものを格納します。
code: bash
$ flask routes
Endpoint Methods Rule
-------- ------- -----------------------
index GET /
static GET /static/<path:filename>
python-dotenv との連携
Flaskで開発しているとき、 新しいターミナルを開くたびに毎回 FLASK_APP を設定するのは面倒です。Flask は python-dotenv をサポートしているので、この拡張モジュールがインストールされていれば、環境変数を設定を自動化することができます。
インストール
code: bash
$ pip install python-dotenv
flask コマンドが実行されると、.env と .flaskenv の中で定義されている環境変数を設定します。
重要:
.flaskenv で設定された内容は.env で上書きされます。
code: .flaskenvの例
FLASK_RUN_PORT=8000
FLASK_APP=apps/app.py
FLASK_DEBUG=1
FLASK_ENV=development
この環境変数は、flask コマンドラインのサブコマンドにも依存しています。
例えば、flask run コマンドは次のオプションがあります。
code: bash
$ flask run --help
Run a local development server.
This server is for development purposes only. It does not provide the
stability, security, or performance of production WSGI servers.
The reloader and debugger are enabled by default if FLASK_ENV=development
or FLASK_DEBUG=1.
Options:
-h, --host TEXT The interface to bind to.
-p, --port INTEGER The port to bind to.
--cert PATH Specify a certificate file to use HTTPS.
--key FILE The key file to use when specifying a
certificate.
--reload / --no-reload Enable or disable the reloader.
(以下略)
flask run --reload に与えるオプションを有効にするためには、
FLASK_RUN_RELOAD=True を環境変数としてセットしておきます。
ここで、悩ましい問題はタイミングです。環境変数が格納されているファイル .flaskenv や .env は、flask.run() が実行されて読み込まれます。
したがって、FLASK_RUN_HOST および FLASK_RUN_PORT の値は、flask.run() の呼び出し時に与える必要がありますが、この段階では読み込まれていないため、もし環境変数として設定されていないとデフォルト値が使われてしまいます。
構成設定ファイル
Pythonのアプリケーションで設定ファイルを扱う方法はいくつかあります。
Flask の場合、もっとも簡単な方法は変数をapp.configのキーとして定義することです。
code: python
app = Flask(__name__)
この定義を外部に設定ファイルとして切り出したくなりますよね。
こんなときは、クラスを使用してクラス変数として格納するとスッキリと定義できるだけでなく、とても拡張性が高くなります。
まず、アプリケーションのための設定クラスConfigをconfig.pyとして保存します。
code: config.py
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'XXX__SECRET_KEY_XXX'
設定情報はConfigクラスのクラス変数として定義されます。 アプリケーションでさらに多くの設定情報が必要になったときも、このクラスに簡単に追加することができます。
また、複数の構成セットが必要になった場合は、そのサブクラスとして作成できます。
設定情報をまとめたconfig.pyモジュールができたので、app.config.from_object()を使ってFlaskに登録します。
code: python
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
デバッグ
Webフレームワークの多くはコンソール出力が抑制されていて、単純にprint()でデバッグライトから情報を得るということができません。
flask ではロギング機能があり、コンソールに情報を出力することができます。
code: Python
app.logger.debug("何かのメッセージ")`
ファイルにロギング
logging モジュールをサポートしているので、同じようにログを取ることができます。
code: python
from logging.handlers import RotatingFileHandler
import os
# ...
if not app.debug:
# ...
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/myapp.log',
maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('myapp start')
コマンドインタフェース
作成したWebアプリケーションをコマンドラインから実行することが簡単になります。
また、Flaskはオプション解析モジュール Click に対応しているので、
オプションを追加することも簡単です。
code: app.py
import click
from flask import Flask
app = Flask(__name__)
@app.cli.command("create-user")
@click.argument("name")
def create_user(name):
pass
if __name__ == '__main__':
app.run(debug=True)
こうして作成したアプリケーションは次のようにコマンドラインで操作できます。
code: bash
$ python app.py create-user admin
次のコードは、サブコマンドuser にcreate をまとめているものです。
code: myapp.py
import click
from flask import Flask
from flask.cli import AppGroup
app = Flask(__name__)
user_cli = AppGroup('user')
@user_cli.command('create')
@click.argument('name')
def create_user(name):
pass
app.cli.add_command(user_cli)
if __name__ == '__main__':
app.run(debug=True)
code: bash
$ python myapp.py user create admin
独自コマンドとして利用するためには、Flaskの初期化処理を@click.group() で、
FlaskGroup を継承させておきます。
code: python
import click
from flask import Flask
from flask.cli import FlaskGroup
def create_app():
app = Flask('myapp')
# other setup
return app
@click.group(cls=FlaskGroup, create_app=create_app)
def cli():
pass
次にsetup.py でentry_pointsを定義します。
code: setup.py
from setuptools import setup
setup(
name='myapp',
...,
entry_points={
'console_scripts': [
'myapp=myapp:cli'
],
},
)
インストールすれば、あとは myapp run で実行できます。
code: bash
$ pip install -e .
$ myapp run
Flask でSSL通信
SSL通信でリモートアクセスを許可するには、SSLを使用してWebフレームワークを起動させる必要があります。はじめに、リモートアクセスを許可するSSL証明書を作成します。
Linux系プラットフォームでは、opensslコマンドを使って、証明書の生成に使用するRSAキーを作成します。
code: bash
$ openssl genrsa -out server.key 2048
このキーを使用して、.csrファイルを生成します。
code: bash
$ openssl req -new -key server.key -out server.csr
最後に、この2つのファイル使用してSSL証明書を作成します。
code: bash
$ openssl x509 -req -days 365 \
-in server.csr -signkey server.key -out server.crt
この server.crtと server.key がディレクトリ ssl に保存されているとすると、
次のように api.run() を設定します。
code: myapp.py
# ...
if __name__ == '__main__':
api.run(host='0.0.0.0', port=5000,
ssl_context=('ssl/server.crt', 'ssl/server.key'),
threaded=True, debug=True)
ブラウザからは、https://WEBサーバのIPアドレス:5000 でアクセスします。
このとき、SSL証明書が既知の認証局によって署名されていないことを示す警告が表示されます。(いわゆるオレオレ認証です)
ポートを334としたいときは root 権限 で実行する必要があります。
HTTP から HTTPS へのリダイレクト
実際の運用では、より堅牢な Apache や nginx などのHTTPサーバから呼び出されるようにするべきです。この場合2つの方法があります。
HTTPサーバで HTTPS へリダイレクト
Flask で HTTPS へリダイレクト
本来であればHTTPサーバでHTTPSへリダイレクトする方が良いのかもしれませんが、
アプリケーションのスケーラビリティーを考慮してロードバランサーなどを導入するときは、
動作するすべてのマシン(クラウドではインスタンサーバ)にSSLの設定をする必用があり、煩雑になってしまうので、FlaskでHTTPSへリダイレクトする方が簡単になります。
公式ドキュメントによれば Flask-Talisman を使うことを勧めています。
インストールする必要があります。
code: bash
$ pip install flask-tailsman
code: python
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app)
参考